#include <malloc.h>
#include <ulib.h>
#include <file.h>
#include <stat.h>
#include <unistd.h>
#include <dir.h>
#include <string.h>


#define BUFSIZE         4096
#define printf(...)		fprintf(1,__VA_ARGS__)

static char
getmode(uint32_t st_mode) {
    char mode = '?';
    if (S_ISREG(st_mode)) mode = '-';
    if (S_ISDIR(st_mode)) mode = 'd';
    if (S_ISLNK(st_mode)) mode = 'l';
    if (S_ISCHR(st_mode)) mode = 'c';
    if (S_ISBLK(st_mode)) mode = 'b';
    return mode;
}

int
cpfile(int srcfd, int destfd) {
    static char buffer[BUFSIZE];
    int ret1, ret2;
    while ((ret1 = read(srcfd, buffer, sizeof(buffer))) > 0) {
        if ((ret2 = write(destfd, buffer, ret1)) != ret1) {
            return ret2;
        }
    }
    return ret1;
}

char*
mergePath(char* start, char* end) {
    int len = strlen(start);
    int len1 = len;
    int yes = 0;
    if (start[len-1] == '/') {
        len += strlen(end);
        yes = 1;
    }
    else {
        len = len + 1 + strlen(end);
    }
    char* ret = malloc(sizeof(char) * (len+1));
    int i;
    for (i = 0; i < len1; ++i) {
        ret[i] = start[i];
    }
    if (!yes) {
        ret[i++] = '/';
        len1++;
    }
    for (; i < len; ++i) {
        ret[i] = end[i-len1];
    }
    ret[i] = '\0';
    return ret;
}

int
cpdir(char* src, char* dest) {
    int ret;
    if ((ret = mkdir(dest)) != 0) {
        return ret;
    }
    DIR *dirp = opendir(src);
    assert(dirp != NULL);
    struct dirent *direntp;
    char* path, *path2;
    while ((direntp = readdir(dirp)) != NULL) {
        if (!strcmp(direntp->name, ".")) continue;
        if (!strcmp(direntp->name, "..")) continue;
        path = mergePath(src, direntp->name);
        int fd;
        if ((fd = open(path, O_RDONLY)) < 0) {
            printf("fd, path = %s\n", path);
            free(path);
            return fd;
        }
        free(path);
        struct stat __stat, *_stat = &__stat;
        int ret = fstat(fd, _stat);
        assert(fd >= 0 && ret == 0);
        char mode = getmode(_stat->st_mode);
        int destfd;
        
        switch(mode) {
            case '-':
                path = mergePath(dest, direntp->name);
                if ((destfd = open(path, O_RDWR | O_CREAT)) < 0) {
                    return destfd;
                }
                cpfile(fd, destfd);
                free(path);
                break;
            case 'd':
                path = mergePath(dest, direntp->name);
                path2 = mergePath(src, direntp->name);
                cpdir(path2, path);
                free(path);
                free(path2);
                break;
            default:
                printf("invalid parameter\n");
                break;
        }
    }
    closedir(dirp);
    return 0;
}

void
usage(void) {
    printf("usage: cp old_file new_file\n");
}

int
main(int argc, char** argv) {
    if (argc < 3) {
        usage();
        exit(-1);
    }
    else {
        int destfd, srcfd;
        if ((srcfd = open(argv[1], O_RDONLY)) < 0) {
            return srcfd;
        }
        struct stat __stat, *_stat = &__stat;
        int ret = fstat(srcfd, _stat);
        assert(srcfd >= 0 && ret == 0);
        char mode = getmode(_stat->st_mode);
        switch(mode) {
            case '-':
                if ((destfd = open(argv[2], O_RDWR | O_CREAT)) < 0) {
                    return destfd;
                }
                cpfile(srcfd, destfd);
                break;
            case 'd':
                //copy dir recursively
                cpdir(argv[1], argv[2]);
                break;
            default:
                printf("invalid parameter %s\n", argv[1]);
                break;
        }
    }
    return 0;
}
